跳到主要内容

MySQL 中的表锁

面试题 1:意向锁的基本概念与作用

面试官:你能解释一下什么是 MySQL 中的意向锁吗?它解决了什么问题?

考察点

  • 对 MySQL 锁机制的理解
  • 多粒度锁的概念
  • 性能优化思维

参考答案

意向锁是 InnoDB 中的一种表级锁,它支持多粒度锁(行级锁与表级锁共存)。意向锁分为两种:

  • 意向共享锁(IS):事务准备对某些行加共享锁
  • 意向排他锁(IX):事务准备对某些行加排他锁

核心作用:提高加表锁的效率,避免遍历整个表来检查行锁。

解决的问题场景

时序图说明

  1. 事务A在获取行锁前,InnoDB自动先获取表级意向锁
  2. 事务B想要获取表锁时,只需检查表级意向锁即可判断冲突
  3. 避免了遍历所有行来检查行锁的低效操作

面试题 2:意向锁的兼容性矩阵

面试官:意向锁之间以及与其他锁之间的兼容性是怎样的?这种设计有什么好处?

考察点

  • 锁兼容性的理解
  • 并发控制机制
  • 系统设计思维

参考答案

兼容性表格

当前锁模式ISIXSX
IS
IX
S
X

设计好处

  1. 高并发性:意向锁不影响行级锁的并发
  2. 高效检测:表锁申请时只需检查意向锁
  3. 自动维护:用户无需手动操作,降低复杂度

面试题 3:意向锁的实际应用场景

面试官:请描述一个具体的业务场景,说明意向锁是如何工作的?

考察点

  • 实际业务理解能力
  • 锁机制在真实场景中的应用
  • 并发问题的分析能力

参考答案

业务场景:电商系统中的订单处理

-- 用户表
CREATE TABLE users (
id INT PRIMARY KEY,
name VARCHAR(50),
balance DECIMAL(10,2)
);

并发场景分析

关键点说明

  1. T1和T3并发执行:意向锁兼容,不同行的操作可以并发
  2. T2被阻塞:表锁与意向锁冲突,保证数据一致性
  3. 效率提升:T2无需检查每一行,直接通过意向锁判断冲突

面试题 4:意向锁与性能优化

面试官:如果没有意向锁,MySQL 需要如何判断表锁与行锁的冲突?这会带来什么性能问题?

考察点

  • 性能分析能力
  • 算法复杂度理解
  • 系统优化思维

参考答案

没有意向锁的情况

性能问题分析

  1. 时间复杂度:O(n),n为表中记录数
  2. I/O开销:需要扫描大量数据页
  3. 锁争用:检查过程中可能产生额外的锁竞争

有意向锁的优化

性能提升

  • 时间复杂度:O(1)
  • 减少I/O:无需扫描数据行
  • 快速判断:立即知道是否存在行锁冲突

面试题 5:Go 中操作 MySQL 的锁机制

面试官:在 Go 应用中,如何正确使用 MySQL 的锁机制?有哪些最佳实践?

考察点

  • Go 语言实际应用能力
  • 数据库连接池管理
  • 事务处理最佳实践

参考答案

package main

import (
"database/sql"
"context"
"time"
_ "github.com/go-sql-driver/mysql"
)

type UserService struct {
db *sql.DB
}

// 悲观锁:适用于冲突频繁的场景
func (s *UserService) UpdateBalanceWithLock(ctx context.Context, userID int64, amount float64) error {
tx, err := s.db.BeginTx(ctx, nil)
if err != nil {
return err
}
defer tx.Rollback()

// SELECT ... FOR UPDATE 会自动获取意向排他锁和行排他锁
var balance float64
err = tx.QueryRowContext(ctx,
"SELECT balance FROM users WHERE id = ? FOR UPDATE", userID).Scan(&balance)
if err != nil {
return err
}

// 业务逻辑处理
newBalance := balance + amount
if newBalance < 0 {
return errors.New("insufficient balance")
}

// 更新余额
_, err = tx.ExecContext(ctx,
"UPDATE users SET balance = ? WHERE id = ?", newBalance, userID)
if err != nil {
return err
}

return tx.Commit()
}

// 乐观锁:适用于冲突较少的场景
func (s *UserService) UpdateBalanceWithVersion(ctx context.Context, userID int64, amount float64) error {
for retry := 0; retry < 3; retry++ {
var balance float64
var version int

// 读取当前值和版本号
err := s.db.QueryRowContext(ctx,
"SELECT balance, version FROM users WHERE id = ?", userID).
Scan(&balance, &version)
if err != nil {
return err
}

newBalance := balance + amount
if newBalance < 0 {
return errors.New("insufficient balance")
}

// 乐观锁更新
result, err := s.db.ExecContext(ctx,
"UPDATE users SET balance = ?, version = version + 1 WHERE id = ? AND version = ?",
newBalance, userID, version)
if err != nil {
return err
}

rowsAffected, _ := result.RowsAffected()
if rowsAffected > 0 {
return nil // 更新成功
}

// 版本冲突,重试
time.Sleep(time.Millisecond * 10)
}

return errors.New("update failed after retries")
}

最佳实践

面试题 6:分布式场景下的锁机制

面试官:在微服务架构中,单纯的 MySQL 锁机制有什么局限性?你会如何设计分布式锁?

考察点

  • 分布式系统理解
  • 架构设计能力
  • 技术选型能力

参考答案

MySQL 锁的局限性

  1. 单机限制:只能在单个 MySQL 实例内生效
  2. 跨服务问题:不同服务连接不同数据库时无效
  3. 网络分区:无法处理网络分区情况

分布式锁设计方案

Go 实现示例

// Redis 分布式锁实现
type RedisLock struct {
client *redis.Client
key string
value string
expire time.Duration
}

func (l *RedisLock) TryLock(ctx context.Context) (bool, error) {
script := `
if redis.call("set", KEYS[1], ARGV[1], "NX", "EX", ARGV[2]) then
return 1
else
return 0
end
`

result, err := l.client.Eval(ctx, script, []string{l.key},
l.value, int(l.expire.Seconds())).Result()

return result.(int64) == 1, err
}

func (l *RedisLock) Unlock(ctx context.Context) error {
script := `
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
`

_, err := l.client.Eval(ctx, script, []string{l.key}, l.value).Result()
return err
}

这些面试题涵盖了从基础概念到实际应用的各个层面,既保留了原有的核心知识点,又增加了大厂常见的深度思考和实践应用方面的考察。通过 mermaid 图表和时序图,能够更直观地展示复杂的锁机制交互过程。